package meta

import (
	"cluster-cache/common"
	"encoding/binary"
	"math"
	"sync"
)

const UpdateIndexFromMaster = 8
const InitClusterSuccess = 9

var KvPool = sync.Pool{
	New: func() interface{} {
		return &common.KeyValueScore{}
	},
}

// KeyValue Protocol format:
//
// * 0           2             4            x          x+2           y        y+8
// * +-----------+--------------+-----------+-----------+------------+---------+
// * |    id     |   key len    | key bytes | value len | value bytes|  score  |
// * +--------------------------------------------------------------------------+
// x = len(key)+4
// y = x+len(value)+2

func DecodeKey(data []byte) []byte {
	pos := 2
	kl := binary.LittleEndian.Uint16(data[pos:])
	pos += 2
	return data[pos:(pos + int(kl))]
}

func Decode(data []byte) (*common.KeyValueScore, int) {
	kvs := KvPool.Get().(*common.KeyValueScore)
	pos := 0
	kvs.Id = binary.LittleEndian.Uint16(data[pos:])
	pos += 2
	switch kvs.Id {
	case 1:
		kl := binary.LittleEndian.Uint16(data[pos:])
		pos += 2
		kvs.Key = data[pos:(pos + int(kl))]
		pos += int(kl)

		vl := binary.LittleEndian.Uint16(data[pos:])
		pos += 2
		kvs.Value = data[pos:(pos + int(vl))]
		pos += int(vl)
		break
	case 2:
		kl := binary.LittleEndian.Uint16(data[pos:])
		pos += 2
		kvs.Key = data[pos:(pos + int(kl))]
		pos += int(kl)
		break
	case 3:
		kl := binary.LittleEndian.Uint16(data[pos:])
		pos += 2
		kvs.Key = data[pos:(pos + int(kl))]
		pos += int(kl)

		vl := binary.LittleEndian.Uint16(data[pos:])
		pos += 2
		kvs.Value = data[pos:(pos + int(vl))]
		pos += int(vl)

		score := binary.LittleEndian.Uint64(data[pos:])
		kvs.Score = math.Float64frombits(score)
		break
	case 4:
		kl := binary.LittleEndian.Uint16(data[pos:])
		pos += 2
		kvs.Key = data[pos:(pos + int(kl))]
		pos += int(kl)

		vl := binary.LittleEndian.Uint16(data[pos:])
		pos += 2
		kvs.Value = data[pos:(pos + int(vl))]
		pos += int(vl)
		break
	default:
		return nil, 0
	}
	return kvs, pos
}

func EncodeAdd(kv *common.KeyValue, buf []byte) int {
	return EncodeAddSingle(kv.Key, kv.Value, buf)
}

func EncodeAddSingle(key, value string, buf []byte) int {
	pos := 0
	binary.LittleEndian.PutUint16(buf[pos:], 1)
	pos += 2
	binary.LittleEndian.PutUint16(buf[pos:], uint16(len(key)))
	pos += 2
	copy(buf[pos:], key)
	pos += len(key)

	binary.LittleEndian.PutUint16(buf[pos:], uint16(len(value)))
	pos += 2
	copy(buf[pos:], value)
	pos += len(value)
	return pos
}

func EncodeDel(key []byte, buf []byte) int {
	pos := 0
	binary.LittleEndian.PutUint16(buf[pos:], 2)
	pos += 2
	binary.LittleEndian.PutUint16(buf[pos:], uint16(len(key)))
	pos += 2
	copy(buf[pos:], key)
	pos += len(key)
	return pos
}

func EncodeZAdd(key []byte, vs *common.ValueScore, buf []byte) int {
	pos := 0
	binary.LittleEndian.PutUint16(buf[pos:], 3)
	pos += 2
	binary.LittleEndian.PutUint16(buf[pos:], uint16(len(key)))
	pos += 2
	copy(buf[pos:], key)
	pos += len(key)

	binary.LittleEndian.PutUint16(buf[pos:], uint16(len(vs.Value)))
	pos += 2
	copy(buf[pos:], vs.Value)
	pos += len(vs.Value)

	binary.LittleEndian.PutUint64(buf[pos:], math.Float64bits(*vs.Score))
	pos += 8
	return pos
}

func EncodeZRmv(key []byte, value []byte, buf []byte) int {
	pos := 0
	binary.LittleEndian.PutUint16(buf[pos:], 4)
	pos += 2
	binary.LittleEndian.PutUint16(buf[pos:], uint16(len(key)))
	pos += 2
	copy(buf[pos:], key)
	pos += len(key)

	binary.LittleEndian.PutUint16(buf[pos:], uint16(len(value)))
	pos += 2
	copy(buf[pos:], value)
	pos += len(value)
	return pos
}

func EncodeKeyValueScore(add bool, key []byte, vs *common.ValueScore, buf []byte) int {
	pos := 0
	if add {
		binary.LittleEndian.PutUint16(buf[pos:], 3)
	} else {
		binary.LittleEndian.PutUint16(buf[pos:], 4)
	}
	pos += 2
	binary.LittleEndian.PutUint16(buf[pos:], uint16(len(key)))
	pos += 2
	copy(buf[pos:], key)
	pos += len(key)

	binary.LittleEndian.PutUint16(buf[pos:], uint16(len(vs.Value)))
	pos += 2
	copy(buf[pos:], vs.Value)
	pos += len(vs.Value)
	binary.LittleEndian.PutUint64(buf[pos:], uint64(*vs.Score))
	pos += 8
	return pos
}

func BuildUpdateIndexFromMaster(index uint64, hash uint64) []byte {
	buf := make([]byte, 18)
	binary.LittleEndian.PutUint64(buf, UpdateIndexFromMaster)
	binary.LittleEndian.PutUint64(buf[2:], index)
	binary.LittleEndian.PutUint64(buf[10:], hash)
	return buf
}

func BuildClusterInitSuccess() []byte {
	buf := make([]byte, 8)
	binary.LittleEndian.PutUint64(buf, InitClusterSuccess)
	return buf
}
